package mobi.smarthosts;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.zip.GZIPOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

public class CompressionHelper {

    public static final String ZIP_EXTENSION = ".zip";

    public static String getTimestamp() {
        return new SimpleDateFormat("yyyy-MM-dd-HHmmss").format(new Date());
    }

    /**
     * Extract a zip file to expected location.
     *
     * @param zipFilePath
     * @param targetLocation
     */
    public static String[] extractZipFile(String zipFilePath, String targetLocation) {

        File zipFile = new File(zipFilePath);
        if (!zipFile.exists()) throw new IllegalArgumentException("Cannot found zip file.");
        if (zipFile.isDirectory()) throw new IllegalArgumentException("Incorrect zip file.");

        File targetFolder = new File(targetLocation);
        if (!targetFolder.isDirectory()) targetFolder.mkdirs();

        byte[] buffer = new byte[1024];
        int nrBytesRead;
        ArrayList<String> files = new ArrayList<String>();
        ZipInputStream zis = null;
        OutputStream os = null;

        try {
            zis = new ZipInputStream(new FileInputStream(zipFile));
            ZipEntry zipEntry = zis.getNextEntry();
            while (zipEntry != null) {
                File extractedFile = new File(targetFolder + "/" + zipEntry.getName());
                if (!extractedFile.getParentFile().exists()) extractedFile.getParentFile().mkdirs();
                files.add(extractedFile.toString());
                os = new FileOutputStream(extractedFile);
                while ((nrBytesRead = zis.read(buffer)) > 0) {
                    os.write(buffer, 0, nrBytesRead);
                }

                //Finish off by closing the streams
                os.flush();
                Utils.closeStream(os);
                zis.closeEntry();
                zipEntry = zis.getNextEntry();
            }
            return files.toArray(new String[] {});
        } catch (IOException ex) {
            String msg = "Extract zip file " + zipFilePath + " failed.";
            Log.e(msg, ex.getMessage());
            throw new SmartHostsException(msg, ex);
        } finally {
            Utils.closeStream(os);
            Utils.closeStream(zis);
        }
    }

    /**
     * Pack a specified folder to a zip file.
     *
     * @param path_from target folder to be packed.
     * @param destination could be expected zip file name or parent folder of zip file.
     *     In later case, the folder name would be used as zip file name.
     */
    public static File packageFolders(String[] sourcePaths, String zipPath) {
        ZipOutputStream zos = null;
        File zipFile = new File(zipPath);
        try {
            if (!zipFile.exists()) zipFile.createNewFile();
            else zipFile.delete();
            zos = new ZipOutputStream(new FileOutputStream(zipFile));
            for (String sourcePath : sourcePaths) {
                File sourceFile = new File(sourcePath);
                if (!sourceFile.exists()) {
                    String msg = "Source folder " + sourcePath + " doesn't exist.";
                    Log.e(msg);
                    throw new IllegalArgumentException(msg);
                }
                addFileToZip(sourcePath, sourceFile, zos, null, zipFile);
            }
        } catch (IOException ex) {
            String msg = "Create zip file " + zipFile + " failed.";
            Log.e(msg, ex.getMessage());
            throw new SmartHostsException(msg, ex);
        } finally {
            Utils.closeStream(zos);
        }
        return zipFile;
    }

    private static void addFileToZip(String sroucePath, File sourceFile,
            ZipOutputStream zos, File originalDir, File ignoreFile) throws IOException {
        // recurse directory
        if (sourceFile.equals(ignoreFile)) return;
        if (sourceFile.isDirectory()) {
            File[] files = sourceFile.listFiles();
            if (files == null) {
                Log.d("List file " + sourceFile + " failed. Try root mode...");
                ShellExecutor.rootCopyFile(sourceFile, getTempDir());
                files = getTempDir().listFiles();
                Log.d("Copy to " + getTempDir() + " temporary for access.");
                // if originalDir is not null, it is in root mode.
                originalDir = sourceFile;
            }
            if (files.length == 0) return;
            for (int i = 0; i < files.length; i++) {
                addFileToZip(sroucePath, files[i], zos, originalDir, ignoreFile);
                if ((i == files.length - 1) && (sourceFile == originalDir)) {
                    Log.d("Exit root mode...");
                    originalDir = null;
                }
            }
            return;
        }

        // add file to zip packable
        String entryLocation = (originalDir == null)
                ? sourceFile.getAbsolutePath()
                : new File(originalDir, sourceFile.getName()).getAbsolutePath();
        Log.d("adding: " + entryLocation);
        entryLocation = entryLocation.substring(sroucePath.length(), entryLocation.length());
        byte[] tmpBuf = new byte[1024];
        FileInputStream in = new FileInputStream(sourceFile);
        zos.putNextEntry(new ZipEntry(entryLocation));
        int len;
        while ((len = in.read(tmpBuf)) > 0) {
            zos.write(tmpBuf, 0, len);
        }
        zos.closeEntry();
        in.close();
        return;
    }

    private static File getTempDir() {
        Configuration conf = Configuration.getInstance();
        if (conf != null) return conf.getTempDir();
        else {
            File tempFolder = new File("/sdcard/temp/");
            if (!tempFolder.exists()) tempFolder.mkdirs();
            return tempFolder;
        }
    }

    public static File compressByGZip(File file) {
        if (file == null || !file.exists() || file.isDirectory()) return null;
        GZIPOutputStream zout = null;
        FileInputStream fin = null;
        try {
            File gzipFile = new File(file.getParentFile(),
                    Utils.getFileNameWithoutExt(file)+".gzip");
            zout = new GZIPOutputStream(new FileOutputStream(gzipFile));
            fin = new FileInputStream(file);

            byte[] buf = new byte[1024];
            int len;
            while ((len = fin.read(buf)) > 0) zout.write(buf, 0, len);

            zout.finish();
            return gzipFile;
        } catch (IOException ex) {
            String msg = "Compress zip file " + file + " failed.";
            Log.e(msg, ex.getMessage());
            throw new SmartHostsException(msg, ex);
        } finally {
            Utils.closeStream(fin);
            Utils.closeStream(zout);
        }
    }

}
